package com.kuangstudy.aop.limiter;

import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.lang.reflect.Method;

/**
 * @author: panghu
 * @Date: 2021/12/26 16:02
 * @Description: 限流的切面
 */

@Aspect
@Component
@Slf4j
@Order(-2)
public class LimiterAspect {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private DefaultRedisScript<Boolean> ipLimitLua;

    // 1、定义切入点
    @Pointcut("@annotation(com.kuangstudy.aop.limiter.AcessLimiter)")
    public void limiterPointcut() {
    }

    // 2、定义通知
    @Before("limiterPointcut()")
    public void beforeLimiter(JoinPoint joinPoint) {
        log.info("限流进来了");
        log.info("LimiterAspect------------------------------------------------");
        // 1: 从连接点拿到方法的签名
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        // 2: 方法前面拿到方法
        Method method = methodSignature.getMethod();
        // 获取类名
        String className = method.getDeclaringClass().getName();
        // 获取类的包名
        String packageName = method.getDeclaringClass().getPackage().getName();
        // 3: 通过方法拿到注解
        AcessLimiter annotation = method.getAnnotation(AcessLimiter.class);
        // 4: 获取注解的方法名
        String methodName = method.getName();

        log.info("className:{},packName:{},methodName:{}", className, packageName, methodName);
        // 5: 获取服务请求的对象
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        HttpServletResponse response = requestAttributes.getResponse();
        // 6: 获取用户的ip地址
        String userIpAdr = RequestUtils.getIpAddr(request);
        log.info("用户的ip地址是:{}", userIpAdr);

        // 7: 通过方法获取注解的参数
        Integer limit = annotation.limit();
        Integer timeout = annotation.timeout();
        // 包名+类名+方法名+ip 作为redis的key，达到唯一的定位作用
        String redisKey = className + "." + methodName + ":" + userIpAdr;
        log.info("rediskey:{}", redisKey);
        log.info("limit:{},timeout:{}", limit, timeout);

        // 8: 请求lua脚本
        Boolean acquired = stringRedisTemplate.execute(ipLimitLua, Lists.newArrayList(redisKey), limit.toString(), timeout.toString());
        log.info("acquired:{}", acquired);
        // 9:如果超过限流的限制
        if (!acquired) {
            // 抛出异常，然后让 全局异常去捕获处理
            throw new RuntimeException("亲，手下留情吖!");
//            // 抛出异常，然后让全局异常去处理
//            response.setCharacterEncoding("UTF-8");
//            response.setContentType("text/html;charset=UTF-8");
//            try (PrintWriter writer = response.getWriter();) {
//                response.getWriter().print("<h1>客官你慢点，请稍后在试一试!!!</h1>");
//            } catch (Exception ex) {
//                throw new RuntimeException("客官你慢点，请稍后在试一试!!!");
//            }
        }

    }

}
